package com.rupertjones.globalcron.domain;

import com.rupertjones.globalcron.domain.audit.Auditable;
import com.rupertjones.globalcron.domain.scheduling.Executable;
import com.rupertjones.globalcron.util.TimeZoneSource;
import org.hibernate.annotations.Cascade;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static java.lang.String.format;

@Entity
@Table(name = "job")
public class JobDescriptor extends BaseEntity implements Auditable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Column(name = "name")
    private String name = null;

    @Column(name = "timezone")
    private String timezone;

    @Column(name = "script")
    private String script;

    @Column(name = "cron")
    private String cron;

    @Column(name = "enabled")
    private boolean enabled = false;

    @Column(name = "last_modified")
    private long lastModified;

    @Column(name = "save_output")
    private boolean saveOutput = false;

    @Column(name = "wait_for_complete")
    private boolean waitForCompletion = false;

    @Column(name = "allow_adhoc_exec")
    private boolean allowAdhocExec = false;

    @Column(name = "last_fire")
    private Date lastFireServer = null;

    @Column(name = "next_fire")
    private Date nextFireServer = null;

    @ManyToOne
    @JoinColumn(name = "host")
    private Host host;

    @Column(name = "version")
    private int version;

    @OneToMany(mappedBy = "job", orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.ALL })
    @Access(value = AccessType.PROPERTY)
    @Cascade({ org.hibernate.annotations.CascadeType.ALL })
    @OrderBy(value = "lastModified desc")
    private List<JobExecutionLog> logs = new ArrayList<JobExecutionLog>();

    @Column(name = "deleted")
    private boolean deleted;

    @Column(name = "wait_after")
    private long waitAfter = 0;

    public DateTime getLastFire() {
        if (lastFireServer != null) {
            return new DateTime(lastFireServer, host.getTimeZone());
        }
        return null;
    }

    public DateTime getNextFire() {
        if (nextFireServer != null) {
            return new DateTime(nextFireServer, host.getTimeZone());
        }
        return null;
    }

    public DateTime getNextFireAtExecutionTimezone() {
        if (getNextFire() != null) {
            return getNextFire().withZone(getTimezoneAsObject());
        }
        return null;
    }

    public DateTime getLastFireAtExecutionTimezone() {
        if (getLastFire() != null) {
            return getLastFire().withZone(getTimezoneAsObject());
        }
        return null;
    }

    public List<JobExecutionLog> getLogs() {
        return logs;
    }

    public void setLogs(List<JobExecutionLog> logs) {
        this.logs = logs;
    }

    public long getWaitAfter() {
        return waitAfter;
    }

    public void setWaitAfter(long waitAfter) {
        this.waitAfter = waitAfter;
    }

    public boolean isSaveOutput() {
        return saveOutput;
    }

    public void setSaveOutput(boolean saveOutput) {
        this.saveOutput = saveOutput;
    }

    public boolean isWaitForCompletion() {
        return waitForCompletion;
    }

    public void setWaitForCompletion(boolean waitForCompletion) {
        this.waitForCompletion = waitForCompletion;
    }

    public long getLastModified() {
        return lastModified;
    }

    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }

    public Host getHost() {
        return host;
    }

    public void setHost(Host host) {
        this.host = host;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public String getName() {
        return name;
    }

    public String getKey() {
        return String.format("%s-%s", getId(), getName().toLowerCase().trim().replace(" ", "_"));
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTimezone() {
        return timezone;
    }

    public DateTimeZone getTimezoneAsObject() {
        return TimeZoneSource.fromId(getTimezone());
    }

    public void setTimezone(String timezone) {
        this.timezone = timezone;
    }

    public void setScript(String script) {
        this.script = script;
    }

    public String getScript() {
        return script;
    }

    public Executable getRunnable() {
        Executable exec = new Executable(this);
        return exec;
    }

    public String getCron() {
        return cron;
    }

    public void setCron(String cron) {
        this.cron = cron;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public boolean canStart() {
        return enabled && !deleted;
    }

    @Override
    public void setId(int id) {
        this.id = id;
    }

    @Override
    public int getId() {
        return this.id;
    }

    public void addOutput(JobExecutionLog jobExecutionLog) {
        this.logs.add(jobExecutionLog);
    }

    public void setDeleted(boolean deleted) {
        this.deleted = deleted;
    }

    public boolean isDeleted() {
        return deleted;
    }

    public boolean isAllowAdhocExec() {
        return allowAdhocExec;
    }

    public void setAllowAdhocExec(boolean allowAdhocExec) {
        this.allowAdhocExec = allowAdhocExec;
    }

    public void incrementVersion() {
        version++;
    }

    public boolean canRun() {
        return canStart() && runOnHost();
    }

    public boolean runOnHost() {
        return host.isEnabled() && runOnThisHost();
    }

    public boolean runOnThisHost() {
        return Host.getCurrentHost().equals(host.getHostname());
    }

    public boolean hasWaitAfter() {
        return waitAfter > 0;
    }

    public void updateLastFire(Date lastFire) {
        this.lastFireServer = lastFire;
    }

    public void updateNextFire(Date nextFire) {
        this.nextFireServer = nextFire;
    }

    public String toReportingLine() {
        StringBuilder buffer = new StringBuilder();
        buffer.append(format("[id: %s] [name: %s] [enabled: %s] [host: %s] [cron: %s] [script: %s] [next fire (@server): %s] [next fire (@%s): %s]",
                getId(), getName(), isEnabled(), getHost().getHostname(), getCron(), getScript(), getNextFire(), getTimezone(), getNextFireAtExecutionTimezone()));
        return buffer.toString();
    }

    @Override
    public String toAuditLog() {
        return toReportingLine();
    }

    @Override
    public AuditLogType getAuditTypeForUpdate() {
        return AuditLogType.EDIT_JOB;
    }

    @Override
    public AuditLogType getAuditTypeForInsert() {
        return AuditLogType.ADD_JOB;
    }

    @Override
    public String toString() {
        return toReportingLine();
    }
}
